package edu.hawaii.duedates;

import argparser.ArgParser;
import argparser.BooleanHolder;
import argparser.IntHolder;
import argparser.StringHolder;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.UnmarshalException;
import javax.xml.bind.Unmarshaller;
import org.xml.sax.SAXParseException;
import edu.hawaii.duedates.library.Collection;
import edu.hawaii.duedates.library.LibraryType;
import edu.hawaii.duedates.user.User;
import edu.hawaii.duedates.userlibraryinfo.UserLibraryInfo;


/**
 * The DueDates class accesses a given library for a given user to acquire the checked out items
 * information.  DueDates receives arguments from the command line that include the library to 
 * request information from, and the user id and the password belonging to required to log in to
 * the library's public web site.
 * 
 * DueDates works with the University of Hawaii and Hawaii State Library systems.
 * 
 * @author Ronn Reeves and Robin E. Raqueno
 */
public final class DueDates {

  /** Repository parameter. */
  private static StringHolder repository = new StringHolder("libraries.xml");

  /** Sort option parameter. */
  private static StringHolder sortOption = new StringHolder("library");

  /** Verbose flag parameter. */
  private static BooleanHolder verbose = new BooleanHolder(Boolean.FALSE);

  /** Within days of being due parameter. */
  private static IntHolder withinDays = new IntHolder(-1);
  
  /** Collection of unmatched command line arguments. */
  //private static String[] unmatchedArgs;
  private static List<String> librariesAndUsersArgs;

  /** Package location. */
  private static final String packagePath = "edu.hawaii.duedates.library";
  
  /** Library collection. */
  private static List<LibraryType> libraries;
  
  /** Library parameter count. */
  private static int libraryCount = 0;
  
  //** Usage example message. */
  private static final String usageMessage = 
    "java -jar DueDates.jar <library code> <user id> <last name> "
    + "[-repository {filename.xml}] [-sort {library | duedate}] [-verbose] [-within {days}]\n";
  
  //** XML file location. */
  private static final String xmlFileLocation = 
    ">>> Please obtain the default libraries.xml data file from "
    + "http://code.google.com/p/duedates-silver/.";
  
  //** XML file validity flag. */
  private static boolean validRepository = Boolean.TRUE;
  
  /** Singleton class constructor. */
  private DueDates() {
  }
  
  
  /**
   * Displays the requested checked out items information for the libraries and users passed to
   * DueDates from the command line.
   * 
   * @param args  The command line arguments passed to the program by the user.
   */
  public static void main(String[] args) {
    
    // Load the command repository and parse command line arguments.
    createCommandListing(args);
    
    // Load the library repository
    importLibraries();
    
    if (validRepository) {
      // Create a collection of user-library info objects.
      UserLibraryInfo uli = createUserLibraryPairs(libraries, librariesAndUsersArgs);
    
      // Request library information.
      System.out.println();
      System.out.println(">>> REQUESTING LIBRARY DATA MAY TAKE SEVERAL SECONDS, "
        + "PLEASE BE PATIENT.");
      System.out.println(">>> RHINO USAGE WARNING IS EXPECTED.  IF IT OCCURS, PLEASE IGNORE.");
      System.out.println();
      uli.requestInfo();
    
      // Display sorted books within the given due date period.
      uli.display(sortOption.value, withinDays.value);
    }
  }


  /**
   * Parse the command line arguments for matched and unmatched values to allowed command line 
   * options.
   * 
   * @param args The command line arguments.
   */
  private static void createCommandListing(String[] args) {
    ArgParser parser = new ArgParser(usageMessage);
      
    try {
      // Create command line options.
      parser.addOption("-repository %s #[filename.xml]#Library repository XML file.\n", repository);
      parser.addOption("-sort %s {library, duedate}#[library | duedate]" +
        "#Sort by library or due date.\n", sortOption);
      parser.addOption("-verbose %v #Enable display of debugging info.\n", verbose);
      parser.addOption("-within %d {[0, 999]}#[Within Day(s)]" +
        "#Number of days until book due date\n", withinDays);

      // Unmatched arguments from command line that should be library and user credential settings.
      String[] unmatchedArgs = parser.matchAllArgs(args, 0, ArgParser.EXIT_ON_ERROR);
      
      // Copy unmatched arguments in to an array list.
      librariesAndUsersArgs = new ArrayList<String>();
      for (String s : unmatchedArgs) {
        librariesAndUsersArgs.add(s);
        if (s.startsWith("-")) {
          libraryCount++;
        }
      }

      // Verify the repository is an xml file.
      boolean isValidRepository = !(repository.value.endsWith(".xml"));
      if (isValidRepository) {
        validRepository = Boolean.FALSE;
        reportXmlFileError(new SAXParseException("", null));
      }
    }

    catch (Exception e) {
      if (libraryCount == 0) {
        System.out.println();
        System.out.println(">>> NO LIBRARY OR USER PARAMETERS HAVE BEEN ENTERED.");
      }

      System.out.println();
      System.out.println(parser.getHelpMessage());
          
      if (verbose.value) {
        System.out.println();
        System.out.println("Stack Trace: ");
        e.printStackTrace();
        System.out.println();
        System.out.println("Additonal Information: ");
        parser.printErrorAndExit(e.getMessage());
      }

      System.exit(0);
    }
  }
  
  
  /**
   * Parses the unmatchedArgs to get the libraries to query from the library repository, and
   * the users for whom to query for.
   *  
   * @param libraries The library repository.
   * @param args The libraries passed from the command line.
   * 
   * @return UserLibraryInfo The UserLibraryInfo object holding the user library pairs.
   */
  private static UserLibraryInfo createUserLibraryPairs(List<LibraryType> libraries,
    List<String> args) {
    UserLibraryInfo uli = new UserLibraryInfo();

    try {
      boolean hasLibraries = (libraryCount > 0);
      boolean hasUsers = !(args.isEmpty());
      
      // Either process libraries provided from command line by user,
      if (hasLibraries) {
        // Processes usage: -library_code user_id password
        for (int index = 0; index < args.size(); index++) {
          for (LibraryType library : libraries) {
            if (library.getCode().equals(args.get(index))) {
              // If the index + 2 is less than the size of the arguments, and neither of the 
              // two following arguments begins with a dash, the user credentials have been 
              // provided.
              if (((index + 2) < args.size()) 
                && !(args.get(index + 1).startsWith("-")) 
                && !(args.get(index + 2).startsWith("-"))) {
                uli.add(new User(args.get(++index), args.get(++index)), library);
              }

              // Otherwise, the number of credentials for this user have not been provided.
              else {
                reportUserCredentialError(library);
              }
            }
          }
        }
      }

      // ...Or only user credentials were passed from command line and are to be assigned
      // to the libraries as ordered in the library repository data file,
      else if (hasUsers) {
        // Processes usage: user_id password (when no -library_code is provided)
        int index = 0;
        while (index < args.size()) {
          for (LibraryType library : libraries) {
            // If the index + 1 is less than the size of the arguments, the user credentials 
            // have been provided.
            if ((index + 1) < args.size()) {
              User usr = new User(args.get(index), args.get(index + 1));
              uli.add(usr, library);
            }

            // Otherwise, the number of credentials for this user have not been provided.
            else {
              reportUserCredentialError(library);
            }
            
            // Increment the index by 2 to move next set of user credentials.
            index += 2;
            
            // All user credentials have been processed and libraries remain, then do not
            // attempt to add more libraries.
            if (index == args.size()) {
              break;
            }
          }
          index = args.size();
        }
      }

      // ...Or no library and user credentials entered on the command line.
      else {
        System.out.println("No libraries or user credentials entered.");
      }
    }
    
    catch (NullPointerException e) {
      System.out.println();
      System.out.println(">>> UNABLE TO CREATE LIBRARY OR USER.  Verify the XML libraries data "
        + "file has the proper structure.");
      System.out.println(xmlFileLocation);
      
      if (verbose.value) {
        System.out.println();
        System.out.println("Stack Trace: ");
        e.printStackTrace();
        System.out.println();
        System.out.println("Additonal Information: ");
        e.getMessage();
      }
    }

    return uli;
  }
  
  
  /**
   * Creates the collection of libraries.
   */
  private static void importLibraries() {
    // The repository of libraries.
    List<LibraryType> libraryList = null;

    // Attempt to import the library repository.
    try {
      // Unmarshall the data from the repository into a library collection.
      JAXBContext jc = JAXBContext.newInstance(packagePath);
      Unmarshaller unmarshaller = jc.createUnmarshaller();
      Collection collection = (Collection) unmarshaller.unmarshal(new File(repository.value));
      Collection.Libraries libraries = collection.getLibraries();
      libraryList = libraries.getLibrary();
    }
    
    catch (UnmarshalException e) {
      reportXmlFileError(e);
    }

    // Report issues to the console
    catch (Exception e) {
      e.printStackTrace();
    }

    // Set the library collection.
    libraries = libraryList;   
  }
  
  
  /**
   * Outputs message indicating the supplied user credentials were insufficient for this library.
   * 
   * @param library The library without valid user credentials being passed.
   */
  private static void reportUserCredentialError(LibraryType library) {
    System.out.println();
    System.out.println(">>> INVALID USER CREDENTIALS FOR "
      + library.getName().toUpperCase() + ".  User credentials must consist of a "
      + "user id and a password.");
    System.out.println(">>> THIS LIBRARY WILL NOT BE QUERIED.");
  }

  
  /**
   * Outputs message indicating the supplied XML file for the library repository is invalid.
   * 
   * @param e The exception being thrown.
   */
  private static void reportXmlFileError(Exception e) {
    validRepository = Boolean.FALSE;
    System.out.println();
    System.out.println(">>> INVALID REPOSITORY FILE.  Must be a valid XML libraries data file.");
    System.out.println(xmlFileLocation);

    if (verbose.value) {
      System.out.println();
      System.out.println("Stack Trace: ");
      e.printStackTrace();
      System.out.println();
      System.out.println("Additonal Information: ");
      e.getMessage();
    }
  }
}